
/******************************************************************************
	Implementacion de la practica con pvm y SSE


	Nota: Cada proceso va a procesar TOTALES/NPROC filas
		Segun como funciona el algoritmo de procesado del frame el proceso
		necesitara ademas (blockheight-1) filas
******************************************************************************/

#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <limits.h>
#include <pmmintrin.h>


#include "pvm3.h"

#define MIN(X, Y)  ((X) < (Y) ? (X) : (Y))


int inicializacion(int *argc, char *argv[], int blockHeight, int blockWidth, int *frameWidth, int *frameHeight, 
                   unsigned char **refFrame, unsigned char **curFrame, int **matchBlock, int *proc);
int blockMatch4x4(const unsigned char* refFrame, const unsigned char* curFrame, 
				  int* results, int frameWidth, int frameHeight, int blockWidth, int blockHeight);

/* nuevas funciones */
void obtener_tids(int *mytid, int *ptid);
void crear_hijos(int mytid, int nproc, int **tids, char **argv, int *root); 
void inicializacion_hijo(int *frameWidth, char **argv, int **results, int *nproc, int *root, int mytid, int ptid);
void paso_parametros(int *tids,int nproc,int *frameHeight, int frameWidth,int ptid, int mytid, int **pfilas, unsigned char **curFrame,
					 unsigned char **refFrame, int blockWidth, int blockHeight, int **posIni, int **posFin, int *fila_ini);
void calcular_posiciones(int **pos_ini,int **pos_fin ,int frameHeight, int blockHeight, int nproc);
void dowork(int ptid, int mytid, int *tids, int nproc, int root, unsigned char **curFrame, unsigned char *refFrame,
			int *results, int frameWidth, int frameHeight, int blockWidth, int blockHeight, int fila_ini);






//TODO Tamano fila, enviarlo padre o leer arg[2] cada hijo?
int main( int argc, char *argv[] ) 
{
	unsigned char *refFrame;
	unsigned char *curFrame;
	int *results;
	int frameWidth;
	int frameHeight;
	int i=0, j=0;
	struct timeval t0, t1, t;
	// Nº de pixels del bloque
	int blockWidth = 4;
	int blockHeight = 4;

	/* nuevas variables */
	int mytid, ptid;
	int nproc;
	int *tids;
	int *pfilas; //Filas que va a analizar cada proceso(formaran los bloques)
	int *posIni, *posFin;//Vectores de inicios y fin, auxiliares para el reparto de trabajo
	int root;//Indica quien es el proceso raiz del grupo
	int fila_ini = 0; //fila de refFrame en la que empieza a procesar cada proceso
	int frameHeightaux; //almacena el valor de frameHeight inicial, solo para mostrar en resultado.

	obtener_tids(&mytid, &ptid);
	
	if(ptid < 0) {
		inicializacion(&argc, argv, blockHeight, blockWidth, &frameWidth, &frameHeight, &refFrame, &curFrame, &results, &nproc);
		assert (gettimeofday (&t0, NULL) == 0);/***TIEMPO 1****/		
		crear_hijos(mytid, nproc, &tids, argv, &root);			
		
		frameHeightaux = frameHeight;//solo para mostrar resultado de memoria
    }else{    	
    	inicializacion_hijo(&frameWidth, argv, &results, &nproc, &root, mytid, ptid);
    }


   	paso_parametros(tids, nproc, &frameHeight, frameWidth, ptid, mytid, &pfilas, &curFrame, &refFrame, blockWidth, blockHeight, &posIni, &posFin, &fila_ini); 

	dowork( ptid, mytid, tids, nproc, root, &curFrame, refFrame, results, frameWidth, frameHeight, blockWidth,blockHeight, fila_ini);

	if (ptid < 0){
		printf ("info: Block (%d,%d) of curFrame matches block (%d,%d) of refFrame (lowsum=%d)\n", 
		i, j, results[0], results[1], results[2]);		
		assert (gettimeofday (&t1, NULL) == 0);/***TIEMPO 2****/			
		timersub(&t1, &t0, &t);
		printf ("info: Memoria       = %ld KB\n", ((2*(frameWidth*frameHeightaux*sizeof(unsigned char)) + (frameWidth*frameHeight*sizeof(unsigned char)) )/1024)  );
		printf ("info: Tiempo total  = %ld seg : %ld mseg\n", t.tv_sec, t.tv_usec/1000);		
	}
	
	
	/* al salir del programa salimos de pvm */
	if( pvm_exit() < 0 ) {
		pvm_perror ("ERROR main(): pvm_exit \n");
	}

	return 0;
}

int blockMatch4x4(const unsigned char* refFrame, const unsigned char* curFrame, 
				  int* results, int frameWidth, int frameHeight, int blockWidth, int blockHeight)
{
	int lowSum = INT_MAX;
	int temSum = 0;
	int i,j,k,l;

	__m128i curReg, refReg;

    curReg = _mm_set_epi8(curFrame[15], curFrame[14], curFrame[13], curFrame[12],
    	                  curFrame[11], curFrame[10], curFrame[9], curFrame[8],
    	                  curFrame[7], curFrame[6], curFrame[5], curFrame[4],
    	                  curFrame[3], curFrame[2], curFrame[1], curFrame[0]);

	for (i=0; i<=frameHeight-blockHeight; i++)
	{
		for (j=0; j<=frameWidth-blockWidth; j++)  
		{
			temSum = 0;

            refReg = _mm_set_epi8(refFrame[(i+3)*frameWidth+(j+3)],refFrame[(i+3)*frameWidth+(j+2)],refFrame[(i+3)*frameWidth+(j+1)],refFrame[(i+3)*frameWidth+(j+0)],
	                  			  refFrame[(i+2)*frameWidth+(j+3)],refFrame[(i+2)*frameWidth+(j+2)],refFrame[(i+2)*frameWidth+(j+1)],refFrame[(i+2)*frameWidth+(j+0)],
	                  			  refFrame[(i+1)*frameWidth+(j+3)],refFrame[(i+1)*frameWidth+(j+2)],refFrame[(i+1)*frameWidth+(j+1)],refFrame[(i+1)*frameWidth+(j+0)],
	                  			  refFrame[(i+0)*frameWidth+(j+3)],refFrame[(i+0)*frameWidth+(j+2)],refFrame[(i+0)*frameWidth+(j+1)],refFrame[(i+0)*frameWidth+(j+0)]);

			refReg = _mm_sad_epu8(refReg,curReg);
			temSum = _mm_extract_epi16(refReg,0) + _mm_extract_epi16(refReg,4);

			
			if (temSum < lowSum)
			{
				lowSum = temSum;
				results[0] = i;
				results[1] = j;
				results[2] = lowSum;
			}
		}
	}


	return 0;
}


int inicializacion(int *argc, char *argv[], int blockHeight, int blockWidth, int *frameWidth, int *frameHeight, 
                   unsigned char **refFrame, unsigned char **curFrame, int **matchBlock, int *nproc)
{
	int i,j;
    unsigned char tmp;
	
    /* Check arguments */
    switch ( (*argc) ) {
		case 4:
			break;
		default:
			fprintf(stderr,"Usage: %s <frameWidth> <frameHeight> <nproc>\n",argv[0]);
			exit(-1);
			break;
    }
	(*frameWidth) = atoi(argv[1]);
	(*frameHeight) = atoi(argv[2]);
	(*nproc) = atoi(argv[3]);	
	printf ("info: frameWidth = %d\n", (*frameWidth));
	printf ("info: frameHeight = %d\n", (*frameHeight));
	if( ((*refFrame)=(unsigned char *) _mm_malloc(sizeof(unsigned char)*(*frameWidth)*(*frameHeight),8)) == NULL ) {
		printf ("error: memoria insuficiente para refFrame\n");
		exit(-1);
	}
	printf ("info: refFrame[%dx%d] allocated\n", (*frameWidth), (*frameHeight));
	if( ((*curFrame)=(unsigned char *) _mm_malloc(sizeof(unsigned char)*(*frameWidth)*(*frameHeight),8)) == NULL ) {
		printf ("error: memoria insuficiente para curFrame\n");
		exit(-1);
	}
	printf ("info: curFrame[%dx%d] allocated\n", (*frameWidth), (*frameHeight));
	srand(1);
	for(i=0;i<((*frameWidth)*(*frameHeight));i++) {
		(*refFrame)[i]=(*curFrame)[i]=(unsigned char) (rand()%256);
	}
	printf ("info: refFrame[%dx%d] initialized\n", (*frameWidth), (*frameHeight));
	printf ("info: curFrame[%dx%d] initialized as a copy of refFrame[%dx%d]\n", (*frameWidth), (*frameHeight), (*frameWidth), (*frameHeight));
	printf ("info: swap block (0,0) and block (4,0) of curFrame\n");
	for( i=0 ; i<blockHeight ; i++ ) {
		for( j=0 ; j<blockWidth ; j++ ) {
		    printf("info: swap curFrame[%ld] and curFrame[%ld]\n", (i*(*frameWidth)+j), ((blockHeight+i)*(*frameWidth)+j));
            tmp = (*curFrame)[i*(*frameWidth)+j];
			(*curFrame)[i*(*frameWidth)+j] = (*curFrame)[(blockHeight+i)*(*frameWidth)+j];
			(*curFrame)[(blockHeight+i)*(*frameWidth)+j] = tmp;
		}
	}
	
	printf ("info: curFrame[%dx%d] initialized\n", (*frameWidth), (*frameHeight));


	if( ((*matchBlock)=(int *) malloc(sizeof(int) * 	3)) == NULL ) {	
		printf ("error: memoria insuficiente para matchBlock\n");
		exit(-1);
	}
	printf ("info: matchBlock[3] allocated\n");
}



/* recibe el root(int) de otro proceso */
void recibir_root(int *root, int mytid, int tid, int msgtag){

	int count  = 1;
	int stride = 1;	
	
    if( pvm_recv( tid, msgtag ) < 0 ) 
        error ("ERROR recibir_root(): pvm_recv \n");
    if( pvm_upkint(root, count, stride ) < 0 ) 
        error ("ERROR recibir_root(): pvm_upkdouble \n");

    printf("[-1]%d:ROOT(INT) message token='%d' received from %d\n",mytid,*root,tid);
}



void inicializacion_hijo(int *frameWidth, char **argv, int **results, int *nproc, int *root, int mytid, int ptid)
{
	/* Hijo lee anchura de lineas */	
	*frameWidth = atoi(argv[1]);
	*nproc = atoi(argv[3]);
	/* Inicializar su vector resultado */
	if( ((*results)=(int *) malloc(sizeof(int)*3)) == NULL ) {
		printf ("error: memoria insuficiente para matchBlock hijo[%d]\n", pvm_mytid());
		exit(-1);
	}
    
    /* Nos unimos al grupo necesario para la reduccion del resultado */
    if(pvm_joingroup("grupo1") < 0)
		error("ERROR inicializacion_hijo(): pmv_joingroup \n");
   
    recibir_root(root, mytid, ptid, 1);   


}


// obtiene el id actual y el del proceso padre en pvm 
void obtener_tids(int *mytid, int *ptid) {

	(*mytid) = pvm_mytid();	
	(*ptid)  = pvm_parent();	
}


/* envia el root(int) a varios procesos */
void enviar_root(int root, int mytid,int *tids, int ntasks,int msgtag){
	
	int count  = 1;
	int stride = 1;

    
    if( pvm_initsend( PvmDataDefault ) < 0 ) 
        error ("ERROR enviar_root(): pvm_initsend \n");
    if( pvm_pkint( &root, count, stride ) < 0 ) 
        error ("ERROR enviar_root(): pvm_pkdouble \n");
    if( pvm_mcast(tids, ntasks, msgtag) < 0 ) 
        error ("ERROR enviar_root(): pvm_mcast \n");

    printf("[-1]%d:ROOT(INT) message token='%d' sent to MULTIPLE TIDS\n", mytid, root);
}


// crea los procesos hijo
void crear_hijos(int mytid, int nproc, int **tids, char **argv, int *root) {

    /* inicializar vector de procesos */
    if ( ((*tids)=(int *)malloc(nproc*sizeof(int))) == NULL ) 
        error ("ERROR crear_hijos(): Not enough memory.\n");		

	(*tids)[0] = mytid;		

	/* crea procesos hijo */
	if( (pvm_spawn("p4_pvm", argv+1, 0, "", nproc-1, &(*tids)[1])) < nproc-1 ) 
		error ("ERROR crear_hijos(): Not enough processes created \n");

	/* creacion del grupo */
    if(*root = pvm_joingroup("grupo1") < 0)
        error("ERROR crear_hijos(): pmv_joingroup \n");

    enviar_root(*root, mytid, (*tids)+1, nproc-1, 1);
}


void enviar_curFrame(int *tids, int ntasks,int frameWidth, int blockWidth, int blockHeight, unsigned char *curFrame, int mytid)
{
	int stride = 1;
	int msgtag = 0;
	
	int i;
	 

    if( pvm_initsend( PvmDataDefault ) < 0 ) 
        error ("ERROR enviar_curFrame(): pvm_initsend \n");

    for( i=0; i<blockHeight; i++){

	    if( pvm_pkbyte( curFrame+(i*frameWidth), blockWidth, stride ) < 0 ) 
	        error ("ERROR enviar_curFrame(): pvm_pkdouble \n");
	}
    
    if( pvm_mcast(tids, ntasks, msgtag) < 0 ) 
        error ("ERROR enviar_curFrame(): pvm_mcast \n");

    printf("[0] %d:curframe sent to MULTIPLE TIDS\n",mytid);
}


void recibir_curFrame(unsigned char **curFrame, int blockWidth, int blockHeight, int ptid, int mytid)
{
	*curFrame = (unsigned char *) _mm_malloc(sizeof(unsigned char *) * blockHeight * blockWidth,8);

	int stride = 1;	
	int msgtag = 0;
	int count = blockWidth * blockHeight;
	
    if( pvm_recv( ptid, msgtag ) < 0 ) 
        error ("ERROR recibir_curFrame(): pvm_recv \n");
    if( pvm_upkbyte(*curFrame, count, stride ) < 0 ) 
        error ("ERROR recibir_curFrame(): pvm_upkbyte \n");

    printf("[0] %d:curframe received\n",mytid);
}

void enviar_filas(int *tids, int nproc, int *posIni, int *posFin, unsigned char *refFrame, int blockHeight, int frameWidth, int mytid ) 
{
	int i;
	int n_filas;
	int stride = 1;

	//empieza en 1 porque 0 es el padre
	for (i = 1; i < nproc; i++)
	{
		//Aqui es donde se le suman las 3 filas a mayores que necesitara cada hijo
		n_filas =  (int)floor((float)(posFin[i]-posIni[i]))+blockHeight;

	    if( pvm_initsend( PvmDataDefault ) < 0 ) 
        	error ("ERROR enviar_filas(): pvm_initsend \n");

        if( pvm_pkint(&n_filas, 1, stride ) < 0 ) 
        	error ("ERROR enviar_filas(): pvm_pkint \n");        

        if( pvm_pkint(&(posIni[i]), 1, stride ) < 0 ) 
        	error ("ERROR enviar_filas(): pvm_pkint \n");     		

        if( pvm_pkbyte( refFrame+(posIni[i]*frameWidth), n_filas*frameWidth, stride ) < 0 ) 
	        error ("ERROR enviar_filas(): pvm_pkbyte \n");

	    if( pvm_send( tids[i], tids[i]) < 0 ) 
            error ("ERROR enviar_filas(): pvm_send \n"); 

	}

	printf("[1] %d:refframe sent to MULTIPLE TIDS\n",mytid);
}


/*	
	Se recibe el numero de filas a procesar por cada hijo, la fila de inicio de proceso
	Y los datos de esas filas
*/
void recibir_filas(unsigned char **refFrame, int *frameHeight, int frameWidth, int ptid, int mytid, int *fila_ini)
{

	int stride = 1;	
	int count;

	
    if( pvm_recv( ptid, mytid ) < 0 ) 
        error ("ERROR recibir_refFrame(): pvm_recv \n");

    if( pvm_upkint(frameHeight, 1, stride ) < 0 ) 
		error ("ERROR recibir_refFrame(): pvm_upkint \n");

    count = (*frameHeight) * frameWidth;

    if( pvm_upkint(fila_ini, 1, stride ) < 0 ) 
		error ("ERROR recibir_refFrame(): pvm_upkint \n");	

	*refFrame = (unsigned char *) _mm_malloc(sizeof(unsigned char *) * count,8);

    if( pvm_upkbyte(*refFrame, count, stride ) < 0 ) 
        error ("ERROR recibir_refFrame(): pvm_upkbyte \n");

    printf("[1] %d:refframe received\n",mytid);
}


void calcular_curFrame(unsigned char* curFrame, int blockHeight, int blockWidth, int frameWidth) 
{
	int i,j;

	for( i=0; i<blockHeight; i++){
			for( j=0; j<blockWidth; j++){
	    			curFrame[(i*blockWidth)+j] = curFrame[(i*frameWidth)+j];
			}
	}
}


void paso_parametros(int *tids,int nproc,int *frameHeight, int frameWidth,int ptid, int mytid, int **pfilas, unsigned char **curFrame,
					 unsigned char **refFrame, int blockWidth, int blockHeight, int **posIni, int **posFin, int *fila_ini)
{
	if( ptid < 0 ){
		enviar_curFrame(tids+1, nproc-1, frameWidth, blockWidth, blockHeight, *curFrame, mytid);
		calcular_posiciones(posIni, posFin, *frameHeight, blockHeight, nproc);
		//Se cambia frameHeight al numero de filas que procesara
		*frameHeight =  (int)floor((float)((*posFin)[0]-(*posIni)[0]))+blockHeight;
        /* curFrame escribe los datos necesarios en las primera posiciones, perdemos el original */
		calcular_curFrame(*curFrame,blockHeight,blockWidth,frameWidth); 
	
		enviar_filas(tids, nproc, *posIni, *posFin, *refFrame, blockHeight, frameWidth, mytid);
	}else{
		recibir_curFrame(curFrame, blockWidth, blockHeight, ptid, mytid);
        recibir_filas(refFrame, frameHeight, frameWidth, ptid, mytid, fila_ini);
	}

}



// calcula los vectores L y U
void calcular_posiciones(int **pos_ini,int **pos_fin ,int frameHeight, int blockHeight,int nproc) {
	int i;	
	int np;
	int rfilas = frameHeight-(blockHeight-1); //para repartir tenemos que tener en cuenta que se mandaran filas a mayores

	if((*pos_ini = (int *)malloc(nproc*sizeof(int))) == NULL)
		error ("ERROR calcular_posiciones(): Not enough memory.\n");	
	if((*pos_fin = (int *)malloc(nproc*sizeof(int))) == NULL)
		error ("ERROR calcular_posiciones(): Not enough memory.\n");		

     /* consecutivo */							
	np = (int) ceil((float)rfilas/(float)nproc);	
	for(i=0; i<nproc; i++){		                        	        
			(*pos_ini)[i]= np*i;
		    (*pos_fin)[i]= MIN((i+1)*np-1, rfilas-1);
	}						
}


/* funcion de reduccion */
void reduce_min(int *datatype, void *x, void *y,int *num, int *info)
{
	int *a = (int *) x;
	int *b = (int *) y;

	if( a[2] > b[2]) {
		a[0] = b[0];
		a[1] = b[1];
		a[2] = b[2];
	}
}


void dowork(int ptid, int mytid, int *tids, int nproc, int root, unsigned char **curFrame, unsigned char *refFrame,
			int *results, int frameWidth, int frameHeight, int blockWidth, int blockHeight, int fila_ini)
{
	unsigned char *pcur;


	pcur = curFrame[0];

	if(ptid < 0)
		printf ("info: Executing the algorithm (procedure blockMatch4x4) ...\n");

 	blockMatch4x4(refFrame,pcur,results,frameWidth,frameHeight,blockWidth,blockHeight);
 	results[0] += fila_ini;

	if(pvm_barrier("grupo1", (nproc)) < 0)
		error("ERROR do_work(): pvm_barrier \n");         

	if(pvm_reduce(reduce_min, results, 3, PVM_INT, 2, "grupo1", root) < 0)
		error("ERROR do_work(): pvm_reduce \n");  

	if(pvm_barrier("grupo1", (nproc)) < 0)
		error("ERROR do_work(): pvm_barrier \n");

	if(pvm_lvgroup("grupo1") < 0)
		error("ERROR do_work(): pvm_lvgroup \n");
}
